window.addEventListener('load', e => {
  const canvas = document.getElementById('canvas');

  const gl = canvas.getContext('experimental-webgl');
  gl.clearColor(0.0, 0.0, 0.0, 1);
  gl.enable(gl.DEPTH_TEST);

  const program = createProgramByMe(gl, v_shader_source, f_shader_source);
  gl.useProgram(program);

  const a_Position = gl.getAttribLocation(program, 'a_Position');
  const a_Normal = gl.getAttribLocation(program, 'a_Normal');
  const a_Texture = gl.getAttribLocation(program, 'a_Texture');
  const a_Face = gl.getAttribLocation(program, 'a_Face');
  const a_Color = gl.getAttribLocation(program, 'a_Color');

  const u_MvpMatrix = gl.getUniformLocation(program, 'u_MvpMatrix');
  const u_NormalMatrix = gl.getUniformLocation(program, 'u_NormalMatrix');
  const u_ModalMatrix = gl.getUniformLocation(program, 'u_ModalMatrix');

  const u_LightColor = gl.getUniformLocation(program, 'u_LightColor');
  const u_LightPosition = gl.getUniformLocation(program, 'u_LightPosition');
  const u_AmbientLight = gl.getUniformLocation(program, 'u_AmbientLight');
  const u_SelectedFaceId = gl.getUniformLocation(program, 'u_SelectedFaceId');

  gl.uniform1i(u_SelectedFaceId, -1);
  gl.uniform3f(u_AmbientLight, 0.2, 0.2, 0.2);

  let faceBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, faceBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, faces, gl.STATIC_DRAW);
  gl.vertexAttribPointer(a_Face, 1, gl.UNSIGNED_BYTE, false, 0, 0);
  gl.enableVertexAttribArray(a_Face);

  let colorBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
  gl.vertexAttribPointer(a_Color, 3, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_Color);

  let normalBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);
  gl.vertexAttribPointer(a_Normal, 3, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_Normal);

  let lightDirection = new Vector3([0.5, 3.0, 4.0]);
  gl.uniform3fv(u_LightPosition, lightDirection.elements);
  gl.uniform3f(u_LightColor, 0.2, 0.2, 0.2);

  let textureBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);
  gl.vertexAttribPointer(a_Texture, 2, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_Texture);

  let positionBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
  gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
  gl.vertexAttribPointer(a_Position, 3, gl.FLOAT, false, 0, 0);
  gl.enableVertexAttribArray(a_Position);

  let indexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);


  let mvpMatrix = new Matrix4();
  mvpMatrix.setPerspective(30.0, canvas.width / canvas.height, 1.0, 100.0);
  mvpMatrix.lookAt(3.0, 3.0, 7.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);

  const draw = (rotateAngle = 0) => {
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    let modalMatrix = new Matrix4();
    modalMatrix.setRotate(rotateAngle, 1.0, 0.0, 0.0);
    modalMatrix.rotate(rotateAngle, 0.0, 1.0, 0.0);
    modalMatrix.rotate(rotateAngle, 0.0, 0.0, 1.0);
    gl.uniformMatrix4fv(u_ModalMatrix, false, modalMatrix.elements);

    let newMvpMatrix = new Matrix4();
    newMvpMatrix.set(mvpMatrix).multiply(modalMatrix);
    gl.uniformMatrix4fv(u_MvpMatrix, false, newMvpMatrix.elements);

    gl.uniformMatrix4fv(u_NormalMatrix, false, new Matrix4().setInverseOf(modalMatrix).transpose().elements);

    gl.drawElements(gl.TRIANGLES, 36, gl.UNSIGNED_BYTE, 0);
  };

  let currentAngle = 0;

  const animate = () => {
    currentAngle = (currentAngle + 0.4) % 360;
    draw(currentAngle);
    requestAnimationFrame(animate);
  };
  animate();

  canvas.addEventListener('mousedown', event => {
    let [x, y] = [event.x, event.y];
    let rect = event.target.getBoundingClientRect();

    let faceId = checkSelected(x - rect.left, rect.bottom - y);
    gl.uniform1i(u_SelectedFaceId, faceId);
    draw(currentAngle);
  });

  let image = new Image();
  image.onload = () => {
    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
    gl.activeTexture(gl.TEXTURE0);
    const texture = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, gl.RGB, gl.UNSIGNED_BYTE, image);

    gl.uniform1i(gl.getUniformLocation(program, 'u_Sampler'), 0);
  };
  image.src = './../../WebGL_Guide_Code/WebGL_Guide_Code/resources/sky.JPG';

  const checkSelected = (x, y) => {
    gl.uniform1i(u_SelectedFaceId, 0);
    draw(currentAngle);
    let pixels = new Uint8Array(4);
    gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
    return pixels[3];
  };
});

const v_shader_source = `
  attribute vec4 a_Position;
  attribute vec4 a_Normal;
  attribute vec2 a_Texture;
  attribute vec4 a_Color;
  attribute float a_Face;

  uniform int u_SelectedFaceId;
  uniform mat4 u_MvpMatrix;
  uniform mat4 u_NormalMatrix; //法向量
  uniform mat4 u_ModalMatrix;

  varying vec3 v_Normal;
  varying vec3 v_Position;
  varying vec2 v_TexCoord;
  varying vec4 v_Color;

  void main(){
    gl_Position = u_MvpMatrix * a_Position;
    v_Normal =normalize(u_NormalMatrix * a_Normal).xyz;
    v_Position = normalize(u_ModalMatrix * a_Position).xyz;
    v_TexCoord = a_Texture;
    if(int(a_Face) == u_SelectedFaceId){
      v_Color = vec4(0.0,1.0,1.0,1.0);
    } else if (u_SelectedFaceId == 0){
      v_Color = vec4(a_Color.rgb, a_Face / 255.0);
    } else {
      v_Color = a_Color;
    }
  }
`;

const f_shader_source = `
  precision mediump float;

  varying vec3 v_Normal;
  varying vec3 v_Position;
  varying vec2 v_TexCoord;

  varying vec4 v_Color;

  //点光源
  uniform vec3 u_LightColor;
  uniform vec3 u_LightPosition; 

  //环境光线
  uniform vec3 u_AmbientLight;
  uniform sampler2D u_Sampler;

  void main(){
      vec3 lightDirection = normalize(u_LightPosition - v_Position);
      float nDotL = max(dot(lightDirection, normalize(v_Normal)), 0.0);
      vec3 diffuse = u_LightColor * v_Color.rgb * nDotL;
      vec3 ambient = u_AmbientLight * v_Color.rgb;
      gl_FragColor = vec4(diffuse + ambient + texture2D(u_Sampler, v_TexCoord).rgb, v_Color.a);
  }
`;

const loadShaderByMe = (gl, type, sourceCode) => {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, sourceCode);
  gl.compileShader(shader);
  let compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (!compiled) {
    let error = gl.getShaderInfoLog(shader);
    console.log('Failed to compile shader: ' + error);
    gl.deleteShader(shader);
    return null;
  }

  return shader;
};

const createProgramByMe = (gl, v_shader_source, f_shader_source) => {
  const program = gl.createProgram();
  gl.attachShader(program, loadShaderByMe(gl, gl.VERTEX_SHADER, v_shader_source));
  gl.attachShader(program, loadShaderByMe(gl, gl.FRAGMENT_SHADER, f_shader_source));
  gl.linkProgram(program);
  let linked = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (!linked) {
    console.log(gl.getProgramInfoLog(program));
  }

  return program;
};

const vertices = new Float32Array([ // Vertex coordinates
  1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, // v0-v1-v2-v3 front
  1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, // v0-v3-v4-v5 right
  1.0, 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, // v0-v5-v6-v1 up
  -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, // v1-v6-v7-v2 left
  -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, // v7-v4-v3-v2 down
  1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0 // v4-v7-v6-v5 back
]);

// Indices of the vertices
const indices = new Uint8Array([
  0, 1, 2, 0, 2, 3, // front
  4, 5, 6, 4, 6, 7, // right
  8, 9, 10, 8, 10, 11, // up
  12, 13, 14, 12, 14, 15, // left
  16, 17, 18, 16, 18, 19, // down
  20, 21, 22, 20, 22, 23 // back
]);

let normals = new Float32Array([
  0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, // v0-v1-v2-v3 front
  1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, // v0-v3-v4-v5 right
  0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v5-v6-v1 up
  -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, // v1-v6-v7-v2 left
  0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, // v7-v4-v3-v2 down
  0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0 // v4-v7-v6-v5 back
]);

let texCoords = new Float32Array([ // Texture coordinates
  1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v0-v1-v2-v3 front
  0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, // v0-v3-v4-v5 right
  1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, // v0-v5-v6-v1 up
  1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, // v1-v6-v7-v2 left
  0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, // v7-v4-v3-v2 down
  0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0 // v4-v7-v6-v5 back
]);

let colors = new Float32Array([ // Colors
  0.32, 0.18, 0.56, 0.32, 0.18, 0.56, 0.32, 0.18, 0.56, 0.32, 0.18, 0.56, // v0-v1-v2-v3 front
  0.5, 0.41, 0.69, 0.5, 0.41, 0.69, 0.5, 0.41, 0.69, 0.5, 0.41, 0.69, // v0-v3-v4-v5 right
  0.78, 0.69, 0.84, 0.78, 0.69, 0.84, 0.78, 0.69, 0.84, 0.78, 0.69, 0.84, // v0-v5-v6-v1 up
  0.0, 0.32, 0.61, 0.0, 0.32, 0.61, 0.0, 0.32, 0.61, 0.0, 0.32, 0.61, // v1-v6-v7-v2 left
  0.27, 0.58, 0.82, 0.27, 0.58, 0.82, 0.27, 0.58, 0.82, 0.27, 0.58, 0.82, // v7-v4-v3-v2 down
  0.73, 0.82, 0.93, 0.73, 0.82, 0.93, 0.73, 0.82, 0.93, 0.73, 0.82, 0.93, // v4-v7-v6-v5 back
]);

let faces = new Uint8Array([ // Faces
  1, 1, 1, 1, // v0-v1-v2-v3 front
  2, 2, 2, 2, // v0-v3-v4-v5 right
  3, 3, 3, 3, // v0-v5-v6-v1 up
  4, 4, 4, 4, // v1-v6-v7-v2 left
  5, 5, 5, 5, // v7-v4-v3-v2 down
  6, 6, 6, 6, // v4-v7-v6-v5 back
]);